/************************* SERVEUR TCP **************************
 * Nom ......... : ServerSelect.c                                *
 * Role ........ : Server de chat. Le code permet les            *
 *                 différentes interactions entre ses clients    *
 * Auteur ...... : Niroj Edward & Jonas Marquis                  *
 * Version ..... : V1 du 01/01/2014                              *
 * Licence ..... : GPL                                           *
 *                                                               *
 * Compilation :                                                 *
 * gcc -pthread -o Server ServerSelect.c                         *
 * Pour exécuter, tapez : ./Server                               *
 ****************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <signal.h> 
#include <pthread.h>

#define PORTS "55555"
#define TAILLE_BUF 256

/* ---------- Variables globales ---------- */
int sockfd, new_fd, rv;
int fdmax; // maximum file descriptor number
char cClientsLogins[20][51];
int iClientsSockets[20];

typedef struct prm_thread { // Données propres aux threads
    int iActual_Pthread; //ID de la thread courante
    int iPrevious_Pthread; //ID de la thread précédente
    int iNum_Client; //ID du client : référence dans le tableau
    int iNum_Socket; //Numéro de socket
    char cBuffer;
} prm_thread;

typedef struct struct_messages { // Définition de la structure d'un message
    int iType; //Définit le type de message
    int iDimData; //Définit la taille des données
    char cData; //Contient les données
} struct_messages;

struct addrinfo hints, *servinfo, *p;
struct sockaddr their_adr;

socklen_t addrlen;
fd_set master; // master file descriptor list
fd_set read_fds; // temp file descriptor list for select()

/* ---------- Traitement des messages ---------- */
void typeMessage(prm_thread *prm) {
    char cLogin[51];
    char cType[1], cDimData[3]; // cBufferfer données client

    char cMessage;
    int iLoginDest;
    char cFichier;
    char cTabLogin[19];
    
    snprintf(cType, 2, "%s", cBuffer);
    iType = atoi(cType);
    printf("%d \n", iType);
    snprintf(iDimData, 4, "%s", cBuffer + 1);
    iDimData = atoi(cDimData);
    printf("%d \n", iDimData);

    switch (iType) {

        case 1: //iType : un message
            //                 char cMessage;
            //                cData = iType + ";" + cLogin + ";" + cMessage;
            pthread_join(prm->iPrevious_Pthread, NULL);
            break;

        case 2: //iType : un fichier
            //                 int iLoginDest;
            //                 char cFichier;
            //                cData = iType + ";" + cLogin + ";" + iLoginDest + ";" + cFichier;
            pthread_join(prm->iPrevious_Pthread, NULL);
            break;

        case 3: //iType : une connexion
//             char cLogin[51];

            snprintf(cLogin, prm->iDimData, "%s", cBuffer + 4);
            clientLogin[prm->iNum_Client] = cLogin;
            snprintf(cData, 0, "%s s'est connectee\n", cLogin);
            clientsSockets[iNum_Client] = sockfd;
            iNum_Client = iNum_Client + 1;
            pthread_join(prm->iPrevious_Pthread, NULL);

            break;

        case 4: //iType : liste des logins
            pthread_join(prm->iPrevious_Pthread, NULL);
            break;

        default:
            perror("identifiant type de données inconnu \n");
    }
}

/* ---------- Envoi des données ---------- */

// Client Unique

void envoi_A_Un_Client(int clientSocket) {
    char cBuffer[256];
    int iTaille_Buffer;

    iTaille_Buffer = sizeof cBuffer;

    if (FD_ISSET(clientSocket, &master)) {
        while (iTaille_Buffer > 0) {
            printf("Lecture de %d octets dans fichier ouvert\n", cpt);
            n = send(clientSocket, cBuffer, nbytes, 0);

            if (n < 0) {
                perror("ERROR writing to socket");
            }
            iTaille_Buffer = iTaille_Buffer - TAILLE_BUF;
            printf("%d octets envoyes au serveur: %s\n", n, buf);
        }

        if (cpt == -1) {
            perror("client: erreur lecture fichier.\n");
            exit(3);
        }
        printf("Fichier envoye avec succes.\n");
    }
}

// Clients Multiples

void envoi_Aux_Clients() {
    char cBuffer[256];
    for (j = 0; j <= fdmax; j++) { // Envoie données à tous les autre clients j
        if (FD_ISSET(j, &master)) {
            // Sauf serveur et client source données
            if (j != sockfd) {
                send(j, cBuffer, nbytes, 0);
            }
        }
    }
}

/* ---------- Processus Communs ---------- */
void f_thread(prm_thread *prm) {
    int nbytes;

    if ((nbytes = recv(i, cBuffer, sizeof cBuffer, 0)) <= 0) { // erreur ou connexion fermée par client
        if (nbytes == 0) {
            printf(" Connexion %d fermée.\n", i);
        } else {
//             perror("+---------- Processus f_thread() ----------+\n"
//                     "| Erreur rencontrée.                       |\n"
//                     "| Logs des paramètres :                    |\n"
//                     "| nbytes = %d                              |\n"
//                     "| i = %d                                   |\n"
//                     "+------------------- EOL ------------------+\n\n",
//                     nbytes, i);
	    printf("+---------- Processus f_thread() ----------+\n| Erreur rencontrée.                       |\n| Logs des paramètres :                    |\n| nbytes = %d                              |\n| i = %d                                   |\n+------------------- EOL ------------------+\n\n", nbytes, i);
        }
        close(i);
        FD_CLR(i, &master); // Supprime ensemble
    } else { // Données reçu du client
        typeMessage((void *) &prm);
    }
}

main() {

    // Déclaration des variables
    int i, j, nbytes;
    pthread_t pthread_id;
    prm_thread prm;

    // Initialisation des variables
    i = 0;
    j = 0;
    nbytes = 0;
    pthread_id = 0;
    prm.iActual_Pthread = 0;
    prm.iPrevious_Pthread = 0;
    prm.iNum_Socket = 0;
    prm.iNum_Client = 0;

    // Clear the master and temp sets
    FD_ZERO(&master);
    FD_ZERO(&read_fds);

    // Création socket et attachement
    memset(&hints, 0, sizeof (hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    // use my IP
    rv = getaddrinfo(NULL, PORTS, &hints, &servinfo);

    if (rv != 0) {
        printf("getaddrinfo: %s\n", gai_strerror(rv));
        return (1);
    }

    // Création socket et attachement
    for (p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
//             perror("+------------ Processus main() ------------+\n| Erreur rencontrée : Socket.             |\n| Logs des paramètres :                    |\n|  - sockfd = %d                           |\n|  - p = %d                                |\n+------------------- EOL ------------------+\n\n", sockfd, p);
	    printf("+------------ Processus main() ------------+\n| Erreur rencontrée : Socket.             |\n| Logs des paramètres :                    |\n|  - sockfd = %d                           |\n|  - p = %d                                |\n+------------------- EOL ------------------+\n\n", sockfd, p);
            continue;
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
//             perror("+------------ Processus main() ------------+\n| Erreur rencontrée : Bind.                |\n| Logs des paramètres :                    |\n|  - sockfd = %d                           |\n|  - p = %d                                |\n+------------------- EOL ------------------+\n\n", sockfd, p);
	    printf("+------------ Processus main() ------------+\n| Erreur rencontrée : Bind.                |\n| Logs des paramètres :                    |\n|  - sockfd = %d                           |\n|  - p = %d                                |\n+------------------- EOL ------------------+\n\n", sockfd, p);
            continue;
        }
        break;
    }

    if (p == NULL) {
//         perror("+------------ Processus main() ------------+\n| Erreur rencontrée : failed to bind.      |\n| Logs des paramètres :                    |\n|  - p = %d                                |\n+------------------- EOL ------------------+\n\n", p);
        printf("+------------ Processus main() ------------+\n| Erreur rencontrée : failed to bind.      |\n| Logs des paramètres :                    |\n|  - p = %d                                |\n+------------------- EOL ------------------+\n\n", p);
        exit(2);
    }

    freeaddrinfo(servinfo); // Libère structure
    listen(sockfd, 5);

    signal(SIGCHLD, SIG_IGN); //SRV_Parallele

    FD_SET(sockfd, &master); // Ajout sockfd à ensemble
    fdmax = sockfd; // Garde valeur max socket

    while (1) {
        read_fds = master; // copie ensemble
        if (select(fdmax + 1, &read_fds, NULL, NULL, NULL) == -1) {
//             perror("+------------ Processus main() ------------+\n"
//                     "| Erreur rencontrée : Select.              |\n"
//                     "| Logs des paramètres :                    |\n"
//                     "|  - select = %d                           |\n"
//                     "|  - fdmax + 1 = %d                        |\n"
//                     "+------------------- EOL ------------------+\n\n",
//                     select, fdmax + 1);
	    printf("+------------ Processus main() ------------+\n| Erreur rencontrée : Select.              |\n| Logs des paramètres :                    |\n|  - select = %d                           |\n|  - fdmax + 1 = %d                        |\n+------------------- EOL ------------------+\n\n", select, fdmax + 1);
            exit(4);
        }

        for (i = 0; i <= fdmax; i++) {

            if (FD_ISSET(i, &read_fds)) {
                if (i == sockfd) {
                    addrlen = sizeof their_adr;
                    new_fd = accept(sockfd, &their_adr, &addrlen);

                    if (new_fd == -1) {
//                         perror("+------------ Processus main() ------------+\n"
//                                 "| Erreur rencontrée : Accept.              |\n"
//                                 "| Logs des paramètres :                    |\n"
//                                 "|  - i = %d                                |\n"
//                                 "|  - new_fd = %d                           |\n"
//                                 "+------------------- EOL ------------------+\n\n",
//                                 i, new_fd);
			  printf("+------------ Processus main() ------------+\n| Erreur rencontrée : Accept.              |\n| Logs des paramètres :                    |\n|  - i = %d                                |\n|  - new_fd = %d                           |\n+------------------- EOL ------------------+\n\n", i, new_fd);
                    } else { // Ajout new_fd à ensemble
                        FD_SET(new_fd, &master);
                        if (new_fd > fdmax) {
                            fdmax = new_fd;
                        }
                        printf("Nouvelle connexion au serveur de %d.\n", i);
                    }
                } else { // gestion données client i
                    pthread_t pthread_id = i;
                    prm.iActual_Pthread = i;
                    pthread_create(&pthread_id, NULL, (void *) &f_thread, (void *) &prm); //Creation de la thread
                } // Fin bloc ELSE client
            } // Fin bloc IF FD_ISSET
            prm.iPrevious_Pthread = i;
        } // Fin bloucle FOR sur i
    } // Fin boucle WHILE
    return 0;
}